package com.zyj.demo.test;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors;


/**
 *
 * 测试 mysql , 与 redis  并发减库存的性能
 */
@SpringBootApplication(scanBasePackages = "com.zyj.demo.test")
public class UpdateStockDemoApplication implements CommandLineRunner {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private JdbcTemplate jdbcTemplate;


    // 库存数
    private Integer stockCount = 9993;


    @Override
    public void run(String... args) throws Exception {
        testByName("mysql");
        testByName("redis");
    }


    /**
     * 根据名称测试并发减库存
     * @param name
     * @throws InterruptedException
     */
    private void testByName(String name) throws InterruptedException {
        System.out.println("开始执行" + name + "并发任务");
        //1. 初始化库存
        initStock(name);
        //2. 执行多次并发减库存
        List<Long> timeList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {//分别统计10次
            long start = System.currentTimeMillis();
            concurrentUpdateStock(name); //
            long end = System.currentTimeMillis();
            long curStock = getCurStock(name);
            System.out.println((i + 1) + "次任务耗时:" + (end - start) + "ms, 当前剩余库存数:" + curStock);
            timeList.add(end - start);
            Thread.sleep(1000);
        }
        System.out.println(timeList.stream().collect(Collectors.summarizingLong(t -> t))); //输出统计结果
    }

    /**
     * 获取当前库存
     */
    private Long getCurStock(String name) {
        if ("mysql".equals(name)) {
           return  jdbcTemplate.queryForObject("select stock from sku where id = 1 ", Long.class);
        } else if ("redis".equals(name)) {
           return Long.valueOf(redisTemplate.opsForValue().get("sku:stock:1"));
        }
        return null;
    }

    /**
     * 初始化库存
     * @param name
     */
    private void initStock(String name) {
        if ("mysql".equals(name)) {
            jdbcTemplate.execute("REPLACE  INTO sku (`id`, `stock`) VALUES ('1', '" + stockCount + "')");
        } else if ("redis".equals(name)) {
            redisTemplate.opsForValue().set("sku:stock:1", stockCount.toString());
        }
        System.out.println("当前库存数:" + getCurStock(name));
    }


    /**
     * mysql减库存线程，  成功返回 非0 数，  库存不足返回 -2
     */
    private Callable<Long> updateStockInMysqlTask = () -> {
        String sql = "update sku set stock = stock-1 where id =1 and stock >=1";
        int count = jdbcTemplate.update(sql);
        if (count == 0) {
            System.out.println("库存不足");
            return   -2L;
        }
        return new Long(count);
    };


    /**
     * redis减库存线程， 成功返回非0， 库存不足返回-2
     */
    private Callable<Long> updateStockInRedisTask = () -> {

        Long getStock = Long.valueOf(redisTemplate.opsForValue().get("sku:stock:1"));

        if (getStock <= 0) {
            System.out.println("发现库存不足");
            return -2L;
        }
//        Long stock =  redisTemplate.execute(new RedisCallback<Long>() {
//            @Override
//            public Long doInRedis(RedisConnection redisConnection) throws DataAccessException {
//                return redisConnection.incr("sku:stock:1".getBytes());
//            }
//        });

        Long stock = redisTemplate.opsForValue().increment("sku:stock:1", -1);

        if (stock < 0) {
            System.out.println("库存不足");
            //因为库存会被减到负数，所以需要加回去
            redisTemplate.opsForValue().increment("sku:stock:1", 1);
            return -2L;
        }
        return stock;
    };


    /**
     * 并发减库存
     * @param name
     * @throws InterruptedException
     */
    private void concurrentUpdateStock(String name) throws InterruptedException {
        int threadsCount = 500; // 线程数
        ExecutorService pool = Executors.newFixedThreadPool(threadsCount); //创建一个固定的线程池
        List<Callable<Long>> tasks = new ArrayList<>();
        for (int i = 0; i < threadsCount * 2; i++) { //2倍于线程数的减库存任务
            if ("mysql".equalsIgnoreCase(name)) {
                tasks.add(updateStockInMysqlTask);
            } else if ("redis".equalsIgnoreCase(name)) {
                tasks.add(updateStockInRedisTask);
            }
        }
        List<Future<Long>> futureList = pool.invokeAll(tasks);

        Long count  =  futureList.stream().filter(f -> {
            try {
                return f.get().longValue() == -2;
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            return false;
        }).count();
        if (count > 0 ) {
            System.out.println("扣库存失败数：" + count);
        }
        pool.shutdown();
    }






    public static void main(String[] args) {
        SpringApplication.run(UpdateStockDemoApplication.class, args).close();
    }
}
